/*-
 * Codec.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 95 $
 * $LastChangedDate: 2007-02-13 16:19:24 +0000 (Tue, 13 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/Codec.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import java.io.IOException;

import com.googlecode.attention.sms.comp.CompressionProcessor;
import com.googlecode.attention.util.IntArrayBuilder;

import static com.googlecode.attention.util.SMSCharacterSet.*;
import static com.googlecode.attention.util.UtilLogStatements.*;

/** * Encodes and decodes SMS user data. *  * @author Stein Gunnar Bakkeby */
public class Codec {

  /**
   * Compression Processor, use this to alter the compression
   * header.
   */
  private CompressionProcessor cp;
  private static Codec instance = null;
  
  /**
   * This will create an instance of the Codec object, this
   * is only necessary if compression is to be used.
   */
  public Codec() {
    cp = new CompressionProcessor();
  }
  
  /**
   * Singleton version of this Codec that can be used if the application
   * is not using more than one compression processor. 
   * 
   * @return Singleton instance of this class
   */
  public static Codec getInstance() {
    if (instance == null) instance = new Codec();
    return instance;
  }
  
  /**
   * This will set the compression processor to use when compressing and
   * decompressing arrays.
   * 
   * @param processor The compression processor to use.
   * @throws NullPointerException If null value is supplied
   */
  public void setCompressionProcessor(final CompressionProcessor processor) {
    if (processor == null) {
      throw new NullPointerException("CompressionProcessor is null");
    }
    cp = processor;
  }

  private static final int[] upperBits = {
//      0xFF, // 0 = B7|B6|B5|B4|B3|B2|B1|B0
      0xFE, // 1 = B7|B6|B5|B4|B3|B2|B1
      0xFC, // 2 = B7|B6|B5|B4|B3|B2
      0xF8, // 3 = B7|B6|B5|B4|B3
      0xF0, // 4 = B7|B6|B5|B4
      0xE0, // 5 = B7|B6|B5
      0xC0, // 6 = B7|B6
      0x80  // 7 = B7 
  };
  
  private static final int[] lowerBits = {
      0x01, // 0 =                   B0
      0x03, // 1 =                B1|B0
      0x07, // 2 =             B2|B1|B0
      0x0F, // 3 =          B3|B2|B1|B0
      0x1F, // 4 =       B4|B3|B2|B1|B0
      0x3F, // 5 =    B5|B4|B3|B2|B1|B0
      0x7F  // 6 = B6|B5|B4|B3|B2|B1|B0
  };
  
  /* End static field values. */
  
  /**
   * Compresses 7bit septets.
   *
   * For more information about how messages are compressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param message The 7bit septets to be compressed
   * @return The compressed string
   */
  public IntArrayBuilder compress7bit(final int[] message) {
    try {
      return cp.compress(CHARSET_GSM7, message);
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Compresses 7bit septets.
   *
   * For more information about how messages are compressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param message The 7bit septets to be compressed
   * @return The compressed string
   */
  public IntArrayBuilder compress7bit(final IntArrayBuilder message) {
    try {
      return cp.compress(CHARSET_GSM7, message.toArray());
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Compresses 8bit octets.
   *
   * For more information about how messages are compressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param message The 8bit octets to be compressed
   * @return The compressed string
   */
  public IntArrayBuilder compress8bit(final int[] message) {
    try {
      return cp.compress(message);
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Compresses 8bit octets.
   *
   * For more information about how messages are compressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param message The 8bit octets to be compressed
   * @return The compressed string
   */
  public IntArrayBuilder compress8bit(final IntArrayBuilder message) {
    try {
      return cp.compress(message.toArray());
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Compresses 16bit strings.
   *
   * For more information about how messages are compressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param message The 16bit string to be compressed
   * @return The compressed string
   */
  public IntArrayBuilder compressUCS2(final int[] message) {
    try {
      return cp.compress(CHARSET_UCS2, message);
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Compresses 16bit strings.
   *
   * For more information about how messages are compressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param message The 16bit string to be compressed
   * @return The compressed string
   */
  public IntArrayBuilder compressUCS2(final IntArrayBuilder message) {
    try {
      return cp.compress(CHARSET_UCS2, message.toArray());
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
//  /**
//   * Decodes 7-bit septets in 8-bit octets as defined in the GSM spec.
//   *
//   * @param ud The user-data with 8-bit octets representing 7-bit data
//   * @param fillBits The number of fill bits, if any
//   * @return The converted message
//   */
//  private static int[] decode7bit(final int[] ud, final int fillBits) {
//    return decode7bit(ud, -1, fillBits);
//  }
  
  /**
   * Decodes 7-bit septets in 8-bit octets as defined in the GSM spec.
   *
   * @param ud The user-data with 8-bit octets representing 7-bit data
   * @param fillBits The number of fill bits, if any
   * @param udl The message length, if known
   * @return The converted message
   */
  public static int[] decode7bit(int[] ud,
                                 int udl,
                                 final int fillBits) {
    final int length = ud.length;
    if (fillBits != 0) {
      final int len = length - 1;
      final int cut = lowerBits[fillBits - 1];
      final int move = 8 - fillBits;
      final int[] udCopy = new int[length];
      for (int f = 0; f < len; f++) {
        udCopy[f] = ud[f] >> fillBits;
        udCopy[f] |= (ud[f + 1] & cut) << move;
      }
      udCopy[len] = ud[len] >> fillBits;
      ud = udCopy;
    }
    
    if (udl < 0) udl = length * 8 / 7;
    final int[] output = new int[udl];
    
    int b = 6, p = 0;
    for (int i = 0; i < udl; i++) {
      switch (b) {
      case 7: // U0
        output[i] = (ud[p] & upperBits[0]) >> 1;
    
        break;
        
      case 6: // L6
        output[i] = ud[p] & lowerBits[b];
        break;
        
      default: // The rest
        output[i] = ((ud[p] & lowerBits[b]) << (6 - b))
                  + ((ud[p - 1] & upperBits[b + 1]) >> (b + 2));
        break;
      }
      if (--b == -1) b = 7;
      else p++;
    }
    
    return output;
  }
  
  /**
   * Converts octets into 8bit alphabet.<BR>
   *
   * @param ud The user-data to be transformed into 8bit alphabet
   * @return The decoded message
   */
  public static int[] decode8bit(final int[] ud) {
    return ud;
  }
  
  /**
   * Converts octets into 16bit alphabet.
   *
   * @param ud The message to be transformed into 16bit alphabet
   * @return The decoded message
   */
  public static int[] decodeUCS2(final int[] ud) {
    final int length = ud.length;
    final int[] output = new int[length / 2];
    int index = 0;
    for (int i = 0; i + 1 < length; i += 2) {
      output[index++] = (ud[i] << 8) | ud[i + 1];
    }
    return output;
  }
  
  /**
   * Decompresses 7bit septets.
   *
   * For more information about how messages are decompressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param ud The 7bit septets to be decompressed
   * @return The decompressed string
   */
  public IntArrayBuilder decompress7bit(final int[] ud) {
    try {
      return cp.decompress(CHARSET_GSM7, new IntArrayBuilder(ud));
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Decompresses 7bit septets.
   *
   * For more information about how messages are decompressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param ud The 7bit septets to be decompressed
   * @return The decompressed string
   */
  public IntArrayBuilder decompress7bit(final IntArrayBuilder ud) {
    try {
      return cp.decompress(CHARSET_GSM7, ud);
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Decompresses 8bit octets.
   *
   * For more information about how messages are decompressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param ud The 8bit octets to be decompressed
   * @return The decompressed string
   */
  public IntArrayBuilder decompress8bit(final int[] ud) {
    try {
      return cp.decompress(new IntArrayBuilder(ud));
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Decompresses 8bit octets.
   *
   * For more information about how messages are decompressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param ud The 8bit octets to be decompressed
   * @return The decompressed string
   */
  public IntArrayBuilder decompress8bit(final IntArrayBuilder ud) {
    try {
      return cp.decompress(ud);
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Decompresses 16bit strings.
   *
   * For more information about how messages are decompressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param ud The 16bit user data to be decompressed
   * @return The decompressed string
   */
  public IntArrayBuilder decompressUCS2(final int[] ud) {
    try {
      return cp.decompress(CHARSET_UCS2, new IntArrayBuilder(ud));
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Decompresses 16bit strings.
   *
   * For more information about how messages are decompressed, see:<BR>
   * Compression algorithm for text messaging services
   * (3GPP TS 23.042 version 5.0.0 Release 5)
   *
   * @param ud The 16bit user data to be decompressed
   * @return The decompressed string
   */
  public IntArrayBuilder decompressUCS2(final IntArrayBuilder ud) {
    try {
      return cp.decompress(CHARSET_UCS2, ud);
    } catch (IOException e) {
      UTIL_DEBUG.log(e);
      return null;
    }
  }
  
  /**
   * Converts 7bit septets into octets. Make sure the user-data
   * has been converted into the GSM 7-bit default alphabet before
   * using this method.
   *
   * <BR><A HREF="http://www.dreamfabric.com/sms/hello.html">
   *              http://www.dreamfabric.com/sms/hello.html</A>
   *
   * @param ud The user-data to be transformed into 7bit septets
   * @param fillBits Fill bits in range 0 - 6
   * @return The encoded message
   */
  public static int[] encode7bitNew(final int[] ud, final int fillBits) {
    /**
     * Cleaner code but slower execution :/
     */
    final int[] upperBits = {
      0x00, // 0 =  
      0x40, // 1 = B6
      0x60, // 2 = B6|B5
      0x70, // 3 = B6|B5|B4
      0x78, // 4 = B6|B5|B4|B3
      0x7C, // 5 = B6|B5|B4|B3|B2
      0x7E, // 6 = B6|B5|B4|B3|B2|B1
      0x7F  // 7 = B6|B5|B4|B3|B2|B1|B0
    }; 
    
    final int[] lowerBits = {
        0x7F, // 0 = B6|B5|B4|B3|B2|B1|B0
        0x3F, // 1 =    B5|B4|B3|B2|B1|B0
        0x1F, // 2 =       B4|B3|B2|B1|B0
        0x0F, // 3 =          B3|B2|B1|B0
        0x07, // 4 =             B2|B1|B0
        0x03, // 5 =                B1|B0
        0x01, // 6 =                   B0
        0x00  // 7 =
    };
    
    final int udl = ud.length;
    final int rest = (udl * 7) % 8;
    int ol = (udl * 7) / 8
           + (rest + fillBits > 8 ? 1 : 0)
           + (fillBits > 0 || rest > 0 ? 1 : 0);
    
    final int[] output = new int[ol--];
    
    int f = (rest + fillBits) % 8;
    int c = (f == 0 ? 0 : 1);
    int u;
    for (u = udl - 1; u >= 0; u--) {
      if (ol == 0) c = 0;
      output[ol]     |= (ud[u] & upperBits[f]) >> 7 - f;
      output[ol - c] |= (ud[u] & lowerBits[f]) << f + 1;
      if (c == 0) c = 1;
      else if (f != 7) ol--;
      
      if (++f == 8) f = 0;
    }
    
    return output;
  }
  
  public static int[] encode7bitFiddle(final int[] ud, final int fillBits) {
    final int udl = ud.length;
    
    int ol = (udl * 7) / 8;
    final boolean rest = (udl * 7) % 8 > 0;
    final boolean fill = fillBits != 0;
    final int ifFillOverflow = ((udl * 7) % 8 + fillBits > 8 ? 1 : 0);
    final int length = ol + (fill || rest ? 1 : 0);
    final int[] output = new int[length + ifFillOverflow];
    
    int b = (((udl * 7) % 8) + fillBits) % 8, p = 0;
//    if (b == -1) b = 6;
    for (int i = 0; i < ol; i++) {
      output[i] = (ud[p] >> b + 1) | ((ud[p + 1] & lowerBits[b]) << 8 - b);
      if (++b == 7) {
        b = 0;
        p++;
      }
      p++;
    }
    System.out.println("b: " + b);
    System.out.println("ol: " + ol);
    System.out.println("rest: " + (udl * 7) % 8);
    System.out.println("fillBits: " + fillBits);
    System.out.println("output.length: " + output.length);
    System.out.println("p: " + p);
    if (ol == output.length) ol--;
    if (p == ud.length) p--;
    if (rest) output[ol] = (ud[p] >> b) | ((ud[p] & lowerBits[b]) << 8 - b);
   
    System.out.println("hello");
      
      
//    if (fill) {
//      final int cut = upperBits[7 - fillBits];
//      final int move = 8 - fillBits;
//      p = 0;
//      for (int i = 0; i <= ol + 1 && i < output.length; i++) {
//        b = output[i] & cut;
//        output[i] ^= b;
//        output[i] <<= fillBits;
//        if (p != 0) output[i] |= p;
//        p = b >> move;
//      }
//    }
    return output;
  }
  
  public static int[] encode7bit(final int[] ud, final int fillBits) {
    final int udl = ud.length;
    
    int ol = (udl * 7) / 8;
    final boolean rest = (udl * 7) % 8 > 0;
    final boolean fill = fillBits != 0;
    final int ifFillOverflow = ((udl * 7) % 8 + fillBits > 8 ? 1 : 0);
    final int length = ol + (fill || rest ? 1 : 0);
    final int[] output = new int[length + ifFillOverflow];
    
    int b = 0, p = 0;
    for (int i = 0; i < ol; i++) {
      output[i] = (ud[p] >> b) + ((ud[p + 1] & lowerBits[b]) << 7 - b);
      if (++b == 7) {
        b = 0;
        p++;
      }
      p++;
    }
    if (rest) output[ol] = ud[p] >> b;
   
      
      
    if (fill) {
      final int cut = upperBits[7 - fillBits];
      final int move = 8 - fillBits;
      p = 0;
      for (int i = 0; i <= ol + 1 && i < output.length; i++) {
        b = output[i] & cut;
        output[i] ^= b;
        output[i] <<= fillBits;
        if (p != 0) output[i] |= p;
        p = b >> move;
      }
    }
    return output;
  }
  
  
  /**
   * Converts 8bit alphabet into octets.
   * <BR> 
   * <BR>8 bit data is user defined
   * <BR>Padding: CR in the case of an 8 bit character set
   * <BR>Otherwise - user defined
   * <BR>Character table: User Specific
   *
   * @param ud The 8bit user-data to be transformed into octets
   * @return The encoded message
   */
  public static int[] encode8bit(final int[] ud) {
    return ud; // no encoding required
  }
  
  /**
   * Converts 16bit data into octets.
   * <BR>
   * <BR>8 bit data is user defined
   * <BR>Padding: CR in the case of an 8 bit character set
   * <BR>Otherwise - user defined
   * <BR>Character table: User Specific
   *
   * @param ud The 16bit user data to be transformed into octets
   * @return The encoded message
   */
  public static int[] encodeUCS2(final int[] ud) {
    final int length = ud.length;
    final int[] output = new int[length * 2];
    int index = 0;
    for (int i = 0; i < length; i++) {
      output[index++] = ud[i] & 0xFF00;
      output[index++] = ud[i] & 0xFF; 
    }
    return output;
  }
}